#include "widget.h"

#define TIMEOUT 100


QVector3D lightPos(-0.0f, 0.0f, -0.0f);
const unsigned int SHADOW_WIDTH = 1024, SHADOW_HEIGHT = 1024;

MyGLWidget::MyGLWidget(QWidget *parent) : QOpenGLWidget(parent)
{
    m_camera.Position = QVector3D(0.0,0.0,30.0);

    m_timer = new QTimer(this);
    connect(m_timer,&QTimer::timeout,this,[&]
    {
        update();
    });
    m_timer->start(TIMEOUT);
    m_time.start();
}

MyGLWidget::~MyGLWidget()
{

}

void MyGLWidget::initializeGL()
{
    initializeOpenGLFunctions(); // 初始化 OpenGL 函数

    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);


    // configure depth map FBO
    // -----------------------
    glGenFramebuffers(1, &depthMapFBO);

    // 创建深度纹理

    // 生成FBO和立方体贴图
    glGenFramebuffers(1, &depthMapFBO);
    glGenTextures(1, &depthCubemap);
    glBindTexture(GL_TEXTURE_CUBE_MAP, depthCubemap);

    // 初始化立方体贴图各面（明确指定精度）
    for (GLuint i = 0; i < 6; ++i) {
        glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_DEPTH_COMPONENT,
                     1024, 1024, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
    }

    // 设置纹理参数
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

    // 绑定FBO并附加深度附件
    glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
    glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthCubemap, 0);

    // 禁用颜色读写
    glDrawBuffer(GL_NONE);
    glReadBuffer(GL_NONE);

    // 检查完整性
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
        std::cerr << "Framebuffer incomplete!" << std::endl;
    }
    glBindFramebuffer(GL_FRAMEBUFFER, 0);


    shader.install(":/shader/shader.vert", ":/shader/shader.frag");
    simpleDepthShader.install(":/shader/depth.vert", ":/shader/depth.frag", ":/shader/depth.geo");


    //创建、绑定纹理
    texture1 = loadTexture(":/img/container.png", false);

    shader.setUniformValue("diffuseTexture", 0);
    shader.setUniformValue("depthMap", 1);
}

void MyGLWidget::paintGL()
{
    float m_far = 25.0;
    glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
    glEnable(GL_DEPTH_TEST);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清除颜色缓冲区

    // 0. Create depth cubemap transformation matrices
    float aspect = (float)1024 / (float)1024;

#if 01
    QMatrix4x4 shadowProj;
    shadowProj.perspective(90.0f, aspect, 1.0, m_far);
    QVector<QMatrix4x4> shadowTransforms;
    QMatrix4x4 m_view;
    m_view.lookAt(lightPos, lightPos + QVector3D( 1.0,  0.0,  0.0), QVector3D(0.0, -1.0,  0.0));
    shadowTransforms.push_back(shadowProj * m_view);
    m_view.lookAt(lightPos, lightPos + QVector3D(-1.0,  0.0,  0.0), QVector3D(0.0, -1.0,  0.0));
    shadowTransforms.push_back(shadowProj * m_view);
    m_view.lookAt(lightPos, lightPos + QVector3D( 0.0,  1.0,  0.0), QVector3D(0.0,  0.0,  1.0));
    shadowTransforms.push_back(shadowProj * m_view);
    m_view.lookAt(lightPos, lightPos + QVector3D( 0.0, -1.0,  0.0), QVector3D(0.0,  0.0, -1.0));
    shadowTransforms.push_back(shadowProj * m_view);
    m_view.lookAt(lightPos, lightPos + QVector3D( 0.0,  0.0,  1.0), QVector3D(0.0, -1.0,  0.0));
    shadowTransforms.push_back(shadowProj * m_view);
    m_view.lookAt(lightPos, lightPos + QVector3D( 0.0,  0.0, -1.0), QVector3D(0.0, -1.0,  0.0));
    shadowTransforms.push_back(shadowProj * m_view);

    // 1. Render scene to depth cubemap
    glViewport(0, 0, width(), height());
    glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
    glClear(GL_DEPTH_BUFFER_BIT);
    simpleDepthShader.use();
    // 获取着色器程序中shadowMatrices数组的统一位置
    for (GLuint i = 0; i < 6; ++i)
    {
        QString locationName = QString("shadowMatrices[%0]").arg(i);
        //locations.append(simpleDepthShader.uniformLocation(locationName.toUtf8().constData()));
        simpleDepthShader.setUniformValue(locationName.toStdString().c_str(), shadowTransforms[i]);
    }

    simpleDepthShader.setUniformValue("far_plane", (GLfloat)m_far);
    simpleDepthShader.setUniformValue("lightPos", lightPos);
    RenderScene(simpleDepthShader);

    glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebufferObject());

    // 2. Render scene as normal
    glViewport(0, 0, width(), height());
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    shader.use();
    QMatrix4x4 projection;
    projection.perspective(m_camera.Zoom, (float)width() / (float)height(), 0.1f, 100.0f);
    QMatrix4x4 view = m_camera.GetViewMatrix();
    shader.setUniformValue("projection", projection);
    shader.setUniformValue("view", view);

    // Set light uniforms
    shader.setUniformValue("lightPos", lightPos);
    shader.setUniformValue("viewPos", m_camera.Position);
    // Enable/Disable shadows by pressing 'SPACE'
    GLboolean shadows = true;
    shader.setUniformValue("shadows", shadows);
    shader.setUniformValue("far_plane", (GLfloat)m_far);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D,  texture1);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_CUBE_MAP, depthCubemap);
    RenderScene(shader);
#endif
}

// renders the 3D scene
// --------------------
void MyGLWidget::RenderScene(Shader &shader)
{
    // Room cube
    QMatrix4x4 model;
    model.setToIdentity();
    model.scale(10.0);
    shader.setUniformValue("model", model);
    glDisable(GL_CULL_FACE);
    shader.setUniformValue("reverse_normals", 1); // A small little hack to invert normals when drawing cube from the inside so lighting still works.
    RenderCube();
    shader.setUniformValue("reverse_normals", 0); // And of course disable it
    glEnable(GL_CULL_FACE);
    // Cubes
    model.setToIdentity();
    model.translate(4.0f, -3.5f, 0.0);
    shader.setUniformValue("model", model);
    RenderCube();
    model.setToIdentity();
    model.translate(2.0f, 3.0f, 1.0);
    model.scale(1.5);
    shader.setUniformValue("model", model);
    RenderCube();
    model.setToIdentity();
    model.translate(-3.0f, -1.0f, 0.0);
    shader.setUniformValue("model", model);
    RenderCube();
    model.setToIdentity();
    model.translate(-1.5f, 1.0f, 1.5);
    shader.setUniformValue("model", model);
    RenderCube();
    model.setToIdentity();
    model.translate(-1.5f, 2.0f, -3.0);
    // 创建一个旋转矩阵，绕Z轴旋转60度
    QMatrix4x4 rotationMatrix;
    rotationMatrix.rotate(60.0f, 1.0f, 0.0f, 1.0f); // 绕Z轴旋转
    model = model * rotationMatrix;
    model.scale(1.5);
    shader.setUniformValue("model", model);
    RenderCube();
}


// renderCube() renders a 1x1 3D cube in NDC.
// -------------------------------------------------
unsigned int cubeVAO = 0;
unsigned int cubeVBO = 0;
void MyGLWidget::RenderCube()
{
    // Initialize (if necessary)
    if (cubeVAO == 0)
    {
        GLfloat vertices[] = {
            // Back face
            -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // Bottom-left
            0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top-right
            0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, // bottom-right
            0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,  // top-right
            -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,  // bottom-left
            -0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,// top-left
            // Front face
            -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom-left
            0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,  // bottom-right
            0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,  // top-right
            0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // top-right
            -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,  // top-left
            -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,  // bottom-left
            // Left face
            -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right
            -0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-left
            -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,  // bottom-left
            -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-left
            -0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f,  // bottom-right
            -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right
            // Right face
            0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-left
            0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-right
            0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-right
            0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,  // bottom-right
            0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,  // top-left
            0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom-left
            // Bottom face
            -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right
            0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, // top-left
            0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,// bottom-left
            0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, // bottom-left
            -0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, // bottom-right
            -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right
            // Top face
            -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,// top-left
            0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right
            0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, // top-right
            0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right
            -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,// top-left
            -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f // bottom-left
        };
        glGenVertexArrays(1, &cubeVAO);
        glGenBuffers(1, &cubeVBO);
        // Fill buffer
        glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
        // Link vertex attributes
        glBindVertexArray(cubeVAO);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)0);
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
        glEnableVertexAttribArray(2);
        glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glBindVertexArray(0);
    }
    // Render Cube
    glBindVertexArray(cubeVAO);
    glDrawArrays(GL_TRIANGLES, 0, 36);
    glBindVertexArray(0);
}



void MyGLWidget::resizeGL(int w, int h)
{
    glViewport(0, 0, w, h); // 设置视口大小
}

unsigned int MyGLWidget::loadTexture(const char *fileName, bool alpha)
{
    unsigned int texture;
    glGenTextures(1, &texture); // 生成纹理 ID
    glBindTexture(GL_TEXTURE_2D, texture); // 绑定纹理

    // 设置纹理参数
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
#if 0
    // 加载图片并转换为 OpenGL 格式
    QImage image;
    if (!image.load(fileName))
    {
        qWarning() << "Failed to load texture image";
        return 0;
    }
    image = QGLWidget::convertToGLFormat(image); // 转换为 OpenGL 格式
    unsigned char *data = image.bits();
    // 生成纹理
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
    glGenerateMipmap(GL_TEXTURE_2D); // 生成多级渐远纹理
#else

    QFile file(fileName);
    file.copy(file.fileName(), QFileInfo(file).fileName());

    int width, height, nrChannels;
    stbi_set_flip_vertically_on_load(true); // tell stb_image.h to flip loaded texture's on the y-axis.
    unsigned char *data = stbi_load(QFileInfo(file).fileName().toStdString().c_str(), &width, &height, &nrChannels, 0);
    if (data)//awesomeface  container
    {
        if(alpha)
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
        else
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D);
    }
    else
    {
        std::cout << "Failed to load texture1" << std::endl;
    }
    stbi_image_free(data);
#endif
    return texture;
}

void MyGLWidget::keyPressEvent(QKeyEvent *event)
{
    float deltatime=TIMEOUT / 1000.0;
    switch(event->key())
    {
    case Qt::Key_Up:ratio += 0.1;break;
    case Qt::Key_Down:ratio -= 0.1;break;
    case Qt::Key_W: m_camera.ProcessKeyboard(FORWARD,deltatime);break;
    case Qt::Key_S: m_camera.ProcessKeyboard(BACKWARD,deltatime);break;
    case Qt::Key_D: m_camera.ProcessKeyboard(RIGHT,deltatime);break;
    case Qt::Key_A: m_camera.ProcessKeyboard(LEFT,deltatime);break;
    }

    if(ratio > 1) ratio = 1;
    if(ratio < 0) ratio = 0;

    makeCurrent();
    shader.bind();
    shader.setUniformValue("ratio",ratio);
    update();
    doneCurrent();
}

void MyGLWidget::mouseMoveEvent(QMouseEvent *event)
{
    qDebug() << "mouseMoveEvent";
    static QPoint lastPos(width()/2,height()/2);
    auto currentPos=event->pos();
    deltaPos=currentPos-lastPos;
    lastPos=currentPos;

    m_camera.ProcessMouseMovement(deltaPos.x(),-deltaPos.y());
    update();
}

void MyGLWidget::wheelEvent(QWheelEvent *event)
{
    qDebug() << "wheelEvent";
    m_camera.ProcessMouseScroll(event->angleDelta().y()/120);
    update();
}
